package org.callisto.midi;

import java.sql.ResultSet;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;

import org.callisto.DbLib;
import org.callisto.Globals;
import org.callisto.handlers.ConsoleOutputHandler;
import org.callisto.handlers.DBOutputHandler;
import org.callisto.handlers.GUIOutputHandler;
import org.callisto.model.Keyboard;

public class MIDIInterface {      
	static boolean inputSuccess;	

	private static MidiDevice getPianoDevice() throws Exception {
		int deviceIndex = -1;
		String deviceIdKey = "Roland Digital Piano";
		System.out.println("Active MIDI devices:");
		MidiDevice.Info[] devices = MidiSystem.getMidiDeviceInfo(); // Query MIDI interface for device list
		for (int i = 0 ; i < devices.length; i++){
			String deviceDesc = devices[i].getName();
			System.out.println(i+ " - " + deviceDesc + " [" + devices[i].getClass().getSimpleName() + "]");
			if (
					deviceIndex == -1 &&
					(
							devices[i].getClass().getSimpleName().equals("MidiInDeviceInfo") ||
							devices[i].getClass().getSimpleName().equals("JSMDevInfo")
					) &&
					devices[i].getName().contains(deviceIdKey)
			) {
				deviceIndex = i;
			}
		}
		if (deviceIndex == -1) {
			throw new Exception("Unable to find device containing key '" + deviceIdKey + "'");
		}
		System.out.println("Bound to MIDI interface (device index:" + deviceIndex + ")");
		return MidiSystem.getMidiDevice(devices[deviceIndex]);
	}
	
	public void listen() throws Exception {
		MidiDevice md = getPianoDevice();
		Transmitter devTrans = md.getTransmitter();
		System.out.println("Created MIDI transmitter instance");
		Keyboard keyboard = new Keyboard();
		devTrans.setReceiver(keyboard);
		//receiver.addObserver(new FileOutputHandler());
		String handlers = Globals.getHandlers();
		if (handlers.contains("C"))	keyboard.addObserver(new ConsoleOutputHandler(keyboard));
		if (handlers.contains("D"))	keyboard.addObserver(new DBOutputHandler(keyboard));
		if (handlers.contains("G")) keyboard.addObserver(new GUIOutputHandler(keyboard));
		//receiver.addObserver(new TCPOutputHandler());
	}
	
	final int ppq = 500; 
	final float mpq = 50;	
	
	public Sequence getSequence() throws Exception {
		Sequencer sequencer = MidiSystem.getSequencer();
		sequencer.setTempoInMPQ(mpq);
		Sequence sequence = new Sequence(Sequence.PPQ,ppq);
		Track track = sequence.createTrack(); // TODO: Need separate track to play chord?
		String sql = "SELECT millis, note, pressure, status FROM history ORDER BY event_id desc LIMIT 1000";
		ResultSet rs = DbLib.query(sql);
		int recordCount = 0;
		int startMillis = 0;
		rs.last();
		while (rs.previous()) {
			if (recordCount == 0) {
				startMillis = rs.getInt("millis");
				System.out.println("Sequence starts at " + startMillis);
			}
			recordCount++;
			ShortMessage message = new ShortMessage();
			int millis = rs.getInt("millis") - startMillis;
			int note = rs.getInt("note");
			int pressure = rs.getInt("pressure");
			int status = rs.getInt("status");
			message.setMessage(status,note,pressure);
			//int tick = (int) (millis * ((float) ppq/ (float) mpq));  // TODO: quantization...
			int tick = millis;
			System.out.println("(millis,tick,note,pressure,status) = (" + millis + "," + tick + "," + note + "," + pressure + "," + status + ")");
			MidiEvent event = new MidiEvent(message,tick);
			track.add(event);
		}
		sequencer.setSequence(sequence);
        sequencer.open();
        sequencer.start();  //TODO: Move actual replay to separate method. Leave only sequence generation here.
        while(true) {
            if(sequencer.isRunning()) {
                try {
                    Thread.sleep(1000); // Check every second
                } catch(InterruptedException ignore) {
                    break;
                }
            } else {
                break;
            }
        }
        // Close the MidiDevice & free resources
        sequencer.stop();
        sequencer.close();
		return sequence;
	}
}
